classdef TestDotsTheComputablesManager < dotsTestCase
    
    properties
        cm;
        logData;
        logGroup;
        recomputeInterval;
    end
    
    methods
        function self = TestDotsTheComputablesManager(name)
            self = self@dotsTestCase(name);
        end
        
        function setUp(self)
            self.setUp@dotsTestCase;
            
            dotsTheComputablesManager.reset;
            topsDataLog.flushAllData;
            dotsTheComputablesManager.sendDataLogUpdate;

            self.cm = dotsTheComputablesManager.theObject;
            self.logData = {'a', 1, 0, false, {'fish', 55}, struct(), @cheese};
            self.logGroup = 'computables manager test';
            self.recomputeInterval = .05;
        end
        
        function tearDown(self)
            topsDataLog.flushAllData;
            dotsTheComputablesManager.sendDataLogUpdate;
            dotsTheComputablesManager.reset;
            self.tearDown@dotsTestCase;
        end
        
        function logSomeData(self)
            % log some known data to topsDataLog
            for ii = 1:length(self.logData)
                topsDataLog.logDataInGroup( ...
                    self.logData{ii}, self.logGroup);
            end
        end
        
        function testSendDataLog(self)
            theLog = topsDataLog.theDataLog;
            
            expectedLength = theLog.length;
            dotsTheComputablesManager.sendDataLogUpdate;
            assertEqual(expectedLength, self.cm.latestLogUpdateLength, ...
                'counterparts should start with same topsDataLog lengths')
            
            self.logSomeData;
            expectedLength = theLog.length;
            dotsTheComputablesManager.sendDataLogUpdate;
            assertEqual(expectedLength, self.cm.latestLogUpdateLength, ...
                'counterparts should update to new topsDataLog lengths')
            
            self.logSomeData;
            expectedLength = theLog.length;
            dotsTheComputablesManager.sendDataLogUpdate;
            assertEqual(expectedLength, self.cm.latestLogUpdateLength, ...
                'counterparts should re-update to new topsDataLog lengths')
        end
        
        function testDoComputation(self)
            % create a dotsComputable to just access the last data log
            % entry as its "computation"
            computable = self.cm.newObject('dotsComputableFunction');
            computable.computeFunction = ...
                @TestDotsTheComputablesManager.lastLogEntry;
            
            theLog = topsDataLog.theDataLog;
            
            logEntry = self.cm.mayComputeComputable(computable);
            assertTrue(isempty(logEntry), ...
                '"computation" should return empty log entry')
            
            self.logSomeData;
            
            dotsTheComputablesManager.sendDataLogUpdate;
            logEntry = self.cm.mayComputeComputable(computable);
            assertEqual(logEntry.item, self.logData{end}, ...
                '"computation" should have accessed known log entry')
            assertEqual(logEntry.group, self.logGroup, ...
                '"computation" should have accessed known log group')
            assertEqual(logEntry.mnemonic, theLog.latestTime, ...
                '"computation" should have accessed most recent entry')
            
            self.logSomeData;
            dotsTheComputablesManager.sendDataLogUpdate;
            logEntry = self.cm.mayComputeComputable(computable);
            assertEqual(logEntry.item, self.logData{end}, ...
                'new "computation" should have accessed known log entry')
            assertEqual(logEntry.group, self.logGroup, ...
                'new "computation" should have accessed known log group')
            assertEqual(logEntry.mnemonic, theLog.latestTime, ...
                'new "computation" should have accessed most recent entry')
            
            topsDataLog.flushAllData;
            dotsTheComputablesManager.sendDataLogUpdate;
            logEntry = self.cm.mayComputeComputable(computable);
            assertTrue(isempty(logEntry), ...
                'post-flush "computation" should return empty log entry')
        end
        
        function testPeriodicRecomputation(self)
            % create a dotsComputable to just return its current time
            computable = self.cm.newObject('dotsComputableFunction');
            computable.computeFunction = @now;
            computable.recomputeInterval = self.recomputeInterval;
            
            self.cm.activateGroup(class(computable));
            pause(2*self.recomputeInterval);
            self.cm.runBriefly;
            timestamp = self.cm.getObjectProperty( ...
                computable, 'computeResult');
            assertFalse(isempty(timestamp), ...
                '"computation" should return a timestamp');
            
            pause(2*self.recomputeInterval);
            self.cm.runBriefly;
            timestamp(2) = self.cm.getObjectProperty( ...
                computable, 'computeResult');
            assertTrue(timestamp(1) < timestamp(2), ...
                'recomputation should return a new timestamp');
            
            pause(2*self.recomputeInterval);
            self.cm.runBriefly;
            timestamp(3) = self.cm.getObjectProperty( ...
                computable, 'computeResult');
            assertTrue(timestamp(2) < timestamp(3), ...
                'recomputation again should return a new timestamp');
            
            self.cm.activateGroup('none');
            self.cm.runBriefly;
            timestamp(4) = self.cm.getObjectProperty( ...
                computable, 'computeResult');
            pause(2*self.recomputeInterval);
            self.cm.runBriefly;
            timestamp(5) = self.cm.getObjectProperty( ...
                computable, 'computeResult');
            assertEqual(timestamp(4), timestamp(5), ...
                'recomputation when inactive should return same timestamps');
        end
    end
    
    methods(Static)
        function lastEntry = lastLogEntry
            allData = topsDataLog.getSortedDataStruct;
            if isempty(allData)
                lastEntry = allData;
            else
                lastEntry = allData(end);
            end
        end
    end
end